Popular Searches
Popular Course Categories
Popular Courses

Java 21 Features Interview Questions: Records, Sealed Classes & Pattern Matching

What Our Students Say
Tricky Java 21 Interview Questions on Records Sealed Classes and Pattern Matching for Experienced Developers 2026

Tricky Java 21 Interview Questions on Records, Sealed Classes, and Pattern Matching for Experienced Java Developers — Complete 2026 Interview Preparation Guide

Why Java 21 Features Are Dominating Interviews in 2026

Java 21 is not just another release. Released in September 2023 as a Long-Term Support version, Java 21 represents the single most significant evolution of the Java language since Java 8 introduced lambdas and streams in 2014. The features it delivers — Records, Sealed Classes, Pattern Matching for switch, Virtual Threads, Text Blocks, Sequenced Collections, and enhanced Switch Expressions — fundamentally change how Java developers model data, write control flow, think about concurrency, and structure their codebases. In 2026, Java 21 LTS is the production baseline at the majority of enterprise Java shops, and senior developer interviews at product companies, banks, fintechs, and technology firms now routinely test deep knowledge of these features.

What makes Java 21 interview questions particularly challenging is that interviewers are not asking for definitions. They are asking tricky, edge-case, why-does-this-work questions that separate developers who have actually used these features in production from those who have only read about them. A developer who can define a Record but cannot explain why Records are not suitable for JPA entities, or who knows that Sealed Classes restrict inheritance but cannot explain exhaustiveness checking in switch expressions, will not pass a senior Java 21 interview at a top product company in 2026.

This guide covers the 20 most challenging and most commonly asked Java 21 interview questions across five feature areas: Records, Sealed Classes, Pattern Matching, Switch Expressions, and Additional Java 21 Features including Virtual Threads, Text Blocks, and Sequenced Collections. Every answer provides the depth and nuance that experienced developer interviews require — not just what each feature does, but why it was designed that way, what its limitations are, and where you should and should not use it in production code.

Whether you are preparing for a senior Java developer role, a full-stack Java position, or a backend architect interview, these Java 21 features are guaranteed to appear in your interview in 2026. This guide gives you the depth to answer every one of them with confidence.

Want expert-led training that takes you from Java fundamentals to senior-level interview readiness with real projects and 100% placement support? Check out JustAcademy's Full Stack Java Developer Bootcamp.

Table of Contents

  1. Java 21 Records Interview Questions
  2. Java 21 Sealed Classes Interview Questions
  3. Pattern Matching Interview Questions
  4. Switch Expressions Interview Questions
  5. Additional Java 21 Features Interview Questions
  6. Frequently Asked Questions

Java 21 Records Interview Questions

 

Records are one of the most widely adopted Java 21 features in production codebases, making them a guaranteed interview topic. Interviewers go far beyond the basics — they test edge cases, design implications, and real-world usage constraints that only developers who have actually built production systems with Records will know.

Question 1. What is a Java Record and what problem does it solve?

A Java Record is a special kind of class introduced as a stable feature in Java 16 and now ubiquitous in Java 21 codebases. It is a transparent, immutable data carrier — a class whose primary purpose is to hold and expose a fixed set of values. Before Records, creating a simple data-holding class in Java required writing a constructor, private final fields, getters for each field, an equals() method, a hashCode() method, and a toString() method — easily 40 to 60 lines of boilerplate for a five-field class. Records reduce this entire structure to a single concise declaration.

When you declare a Record, you specify the record name and its components in parentheses. The Java compiler automatically generates the canonical constructor accepting all components, private final fields for each component, public accessor methods named after each component without a get prefix, an equals() implementation comparing all components by value, a hashCode() implementation consistent with equals(), and a toString() showing all component names and their values in a readable format.

Records solve two interconnected problems. First, they eliminate the verbosity and error-proneness of boilerplate data classes where mistakes in manually written equals() or hashCode() methods cause subtle bugs that are extremely difficult to debug. Second, they communicate intent explicitly — declaring a Record tells every developer reading the code that this class is a pure data carrier with no hidden mutable state, no business logic, and no lifecycle. This clarity of intent is as valuable in large codebases as the boilerplate reduction itself.

The key interviewer follow-up on this question is about what Records are not suitable for. Records are not JavaBeans — they do not follow the convention of no-argument constructors and setter methods. Records are not suitable for JPA entities, which require a no-argument constructor, mutable state for dirty tracking, and the ability to be proxied. Records are not suitable when the class needs to extend another class because Records implicitly extend java.lang.Record and Java does not support multiple inheritance of classes.

Question 2. What is the difference between a compact constructor and a canonical constructor in a Record?

The canonical constructor is the standard constructor that accepts all Record components as parameters and assigns them to the corresponding fields. The Java compiler generates this constructor automatically, but you can define your own explicit canonical constructor with the full parameter list to add validation or normalization logic before the fields are assigned.

The compact constructor is a syntax specific to Records that omits the parameter list entirely and also omits the explicit field assignments. In a compact constructor, the component parameters are implicitly in scope with the same names as the components, and the field assignments happen automatically after the compact constructor body finishes executing. This means you write only the validation or transformation logic without repeating the full parameter list or the assignment statements.

The compact constructor is the idiomatic Java 21 pattern for adding validation to Records. If a component value is invalid, you throw an exception in the compact constructor and the Record is never created in an invalid state. You can also normalize component values in a compact constructor — for example, trimming whitespace from a String component, converting a List component to an unmodifiable copy using List.copyOf(), or converting a null collection to an empty collection. After your compact constructor body runs, the compiler inserts the assignments from the current parameter values to the fields, so reassigning a parameter variable within the compact constructor changes what value gets stored in the field.

The critical distinction for experienced developer interviews is timing. In a compact constructor, the field assignments happen after your code runs and use the current values of the parameters at that point. This means you can reassign parameter variables inside the compact constructor to normalize stored values, but you cannot read this.componentName within the compact constructor body because the fields have not been assigned yet at that point. Attempting to access this.componentName in a compact constructor body produces a compile error.

Question 3. Can Records implement interfaces and can they extend classes?

Records can implement interfaces, and this is a powerful and commonly used pattern in production Java 21 applications. A Record that implements an interface can be used polymorphically wherever the interface type is expected. This capability is particularly powerful when combined with Sealed Interfaces to create type-safe, closed hierarchies of immutable data classes where each sealed interface implementation is a Record, giving you exhaustive pattern matching over a set of immutable data types with all the boilerplate generated automatically.

A practical production example is a domain event hierarchy. You define a sealed interface DomainEvent with permitted Record implementations — OrderPlaced containing orderId and customerId, PaymentProcessed containing orderId and amount, OrderShipped containing orderId and trackingNumber. Each Record implements DomainEvent and carries only the data relevant to that specific event type. When you process these events in a switch expression, the compiler enforces exhaustiveness — you must handle every Record implementation or the code does not compile. Adding a new event type to the sealed interface produces compile errors in every switch expression that processes events, ensuring no new event type is silently ignored.

Records cannot extend any class other than java.lang.Record, which they implicitly extend. This is by design. Records have fixed structure and semantics that would be broken by allowing arbitrary class inheritance, because a parent class could introduce mutable instance fields that violate the Record's transparency guarantee. Records are also implicitly final — no class can extend a Record. This finality ensures that when you have a Record reference, you always know exactly what you are getting. You cannot receive an unexpected subclass with different behavior or additional hidden state.

Question 4. Why are Records not suitable for JPA entities and what should you use instead?

This is one of the most commonly asked Java 21 Records interview questions for experienced Spring Boot developers because it tests practical knowledge that only comes from actually attempting to use Records with JPA, not from reading about Records theoretically.

JPA entity requirements are fundamentally incompatible with Record semantics in three specific ways. First, JPA requires a no-argument constructor so that the JPA implementation can create entity instances using reflection without providing constructor arguments. This is necessary for Hibernate and other JPA providers to instantiate entities when loading data from the database. Records only have a canonical constructor that requires all component values — there is no way to define a no-argument constructor in a Record because all fields are final and must be assigned at construction time.

Second, JPA requires mutable state. The JPA implementation needs to set field values after construction for lazy loading, proxy generation, and dirty tracking during the entity lifecycle. Hibernate's dirty checking mechanism compares field values before and after a transaction to determine what SQL UPDATE statements to generate. Record fields are final and immutable after construction — no mechanism exists for a JPA proxy to modify field values on a Record instance.

Third, JPA uses proxy-based subclassing for lazy loading of associations. Hibernate generates dynamic subclasses of entity classes using CGLIB or Byte Buddy to intercept field access and trigger lazy loading when associations are first accessed. Records are implicitly final and cannot be subclassed, making JPA proxy generation impossible.

For JPA use cases, continue using regular classes annotated with @Entity. For read-only query projections where you want Records' conciseness, use interface-based projections with Spring Data JPA or use Records as constructor expression targets in JPQL queries — Records work perfectly as result types for queries even though they cannot serve as entity classes. The pattern of using an @Entity class for persistence and a Record for the service layer and API layer is the recommended approach in 2026.

Question 5. How do you add custom methods to a Record and what are the constraints?

Records can contain instance methods, static methods, and static fields despite being primarily data carriers. These additions allow Records to provide computed properties, factory methods, and utility behavior without requiring consumers to write that logic externally every time they use the Record.

Custom instance methods are added to a Record exactly as they would be to a regular class by declaring them in the Record body. A method that computes a derived value from the Record's components is the most common pattern. For example, a Money Record with amount and currency components might have an add method that takes another Money record and returns a new Money with the amounts summed, after validating that the currencies match. This method uses the component values without storing any additional state and is consistent with the Record's immutable nature — it returns a new Record rather than modifying the existing one.

Static methods in Records serve as factory methods and utility functions. A static factory method named of is a common convention for Records that benefit from input validation or alternative construction syntax. A static from method that converts a different data representation (like a Map or a database row) into a Record instance is another valuable pattern.

The constraint that Records cannot have instance fields beyond the declared components is enforced by the compiler at compile time. All instance state in a Record must be declared as components in the record header. If you attempt to declare an additional instance field in the record body, the code produces a compile error. This constraint is fundamental to the Record's transparency guarantee — every piece of instance state is visible through the component accessors, with no hidden mutable state that would violate the immutability contract.

Java 21 Sealed Classes Interview Questions

 

Sealed Classes are architecturally more significant than they appear on the surface. They change the expressiveness of the Java type system in ways that enable entirely new design patterns, and interview questions on Sealed Classes consistently go into their interaction with pattern matching and their impact on API design.

Question 6. What is a Sealed Class in Java 21 and what problem does it solve?

A Sealed Class or Sealed Interface explicitly restricts which other classes or interfaces may extend or implement it using the permits keyword. Every permitted subclass must be in the same package or module as the sealed class. Each permitted subclass must be declared with one of three modifiers: final if it should not be extended further, sealed if it should itself restrict its own subtypes, or non-sealed if it explicitly opens itself back up for unrestricted extension.

Sealed Classes solve a problem that Java's type system previously could not express: the need for a closed, exhaustive, and compiler-verifiable set of subtypes. Before Sealed Classes, any public abstract class or interface could be extended by any class in any package at any time. Library authors had no mechanism to express that a type hierarchy was complete and intentionally finite. Developers consuming that hierarchy could not safely write exhaustive switch statements because new subtypes might be added in future library versions without warning.

The most important consequence of Sealed Classes in Java 21 is enabling exhaustive pattern matching in switch expressions. When you switch over a reference of a sealed type, the Java compiler knows the complete set of permitted subtypes at compile time and can verify that every possible concrete type is handled by some case label. If you miss a permitted subtype, the switch expression does not compile. This compile-time enforcement eliminates an entire category of runtime bugs where new subtypes are added to a hierarchy but switch statements in other parts of the codebase are not updated, silently executing incorrect default behavior.

Question 7. What is the difference between sealed, final, and non-sealed in Java 21?

These three modifiers control the inheritance characteristics of classes within and immediately around a sealed hierarchy. Understanding the precise semantics of each and how they combine is a key differentiator in senior Java 21 interviews.

The sealed modifier introduces a restricted hierarchy. A sealed class or interface uses the permits clause to enumerate exactly which direct subtypes are allowed. The compiler enforces two-way compliance: no unlisted class may extend the sealed type, and every listed permitted class must actually extend it. The sealed modifier is used on the root of the hierarchy and on any intermediate node that itself wants to restrict its own subtypes further.

The final modifier on a permitted subclass terminates the hierarchy at that class. A final permitted subclass can have no subclasses of its own — it is a leaf node in the sealed hierarchy. Records are implicitly final, which is why Records and Sealed Interfaces compose so naturally. A sealed interface whose permitted implementations are all Records produces a completely flat, leaf-terminated hierarchy that the compiler can exhaustively check with maximum precision.

The non-sealed modifier is an explicit escape hatch that deliberately breaks open the hierarchy at a specific node. A non-sealed permitted subclass can be extended by any class anywhere, without restriction — the same openness as a traditional abstract class. Pattern matching over a non-sealed subtype cannot guarantee exhaustiveness because the compiler cannot enumerate all possible extensions of a non-sealed class. This modifier exists for intentional extensibility points within an otherwise closed hierarchy — the type system designer is saying that while most of the hierarchy is closed and known, this particular branch is intentionally left open for external extension.

Question 8. How do Sealed Classes enable better API design compared to traditional abstract classes?

Traditional abstract classes and interfaces in Java express an open extensibility contract — any developer anywhere can create new subtypes at any time. This openness is valuable for frameworks and extension points designed for customization. It is actively harmful for types that represent a finite and known set of domain concepts: the possible outcomes of an operation, the states of a state machine, the variants of a domain event, the nodes in an abstract syntax tree.

Sealed Classes enable a closed extensibility contract — you explicitly enumerate all permitted subtypes and the compiler enforces that the set is complete and that all consumers handle the complete set. This closed nature enables three specific and measurable API design improvements. First, exhaustive pattern matching in switch expressions means consumers of your API are compile-time forced to handle all possible types rather than relying on a default case that silently ignores new additions. Second, the API communicates the complete set of possibilities in the type declaration itself, making the code self-documenting in a way that Javadoc cannot achieve because documentation can go unread but the compiler cannot be ignored. Third, maintainability improves dramatically because adding a new permitted subtype to the sealed hierarchy produces compile errors in all switch expressions that need updating, rather than silently executing default behavior that may be incorrect for the new subtype.

The canonical production use cases for Sealed Classes in Java 21 codebases are result types representing either success with a value or failure with an error, domain event hierarchies where each event type carries specific data as a Record, abstract syntax trees for parsers and compilers where each node type represents a specific syntactic construct, and API response types where the possible responses form a defined and finite set.

Pattern Matching Interview Questions

 

Pattern Matching is the feature that makes Sealed Classes most powerful in practice and is consistently the most deeply tested topic in experienced Java 21 developer interviews at product companies in 2026.

Question 9. What is Pattern Matching for instanceof and how does it differ from traditional instanceof?

Traditional instanceof required two separate steps: check the type and then explicitly cast. You wrote if (shape instanceof Circle) and then on the next line Circle circle = (Circle) shape. This two-step process was redundant because you had already verified the type with instanceof — the subsequent cast added verbosity and a potential source of bugs if the variable in the cast differed from the variable in the instanceof check, either through a typo or through copy-paste in a long conditional chain.

Pattern Matching for instanceof combines the type check and the binding variable declaration into a single expression. You write if (shape instanceof Circle circle) and the name circle is available as a Circle-typed binding variable within the scope where the pattern match succeeded. The compiler performs the cast automatically and enforces correct scoping — circle is only accessible within the branch where the instanceof check returned true. The compiler makes it impossible to use circle where the type check has not been confirmed because the scoping rules prevent access to the binding variable outside the confirmed scope.

The deeper significance for experienced developer interviews is flow-sensitive type narrowing. After a successful pattern match, the compiler narrows the type of the binding variable to the matched type and uses this narrowed type for all subsequent references within that scope. This allows you to call Circle-specific methods on circle without any cast, with the compiler guaranteeing type safety at compile time rather than relying on ClassCastException to catch errors at runtime. In negated conditions, the type information flows into the false branch — after if (obj instanceof String s) the binding variable s is accessible in the then-branch, and within the else-branch the compiler knows obj is definitely not a String, which enables further narrowing in subsequent pattern matches.

Question 10. Explain Pattern Matching for switch in Java 21 with an example of guarded patterns.

Pattern Matching for switch extends the switch expression to support type patterns as case labels. Each case can match a specific type and bind the matched object to a pattern variable of that type, enabling type-safe access to type-specific methods and properties within the case body without any explicit casting. This replaces chains of if-instanceof-cast-else blocks with a single, clean, compiler-verified switch expression.

A practical production example is processing a sealed Shape hierarchy. The switch expression has one case for each permitted Shape implementation. The case label declares both the type pattern and a binding variable. Within the case body, the binding variable is already typed correctly with no casting required, and all type-specific methods are accessible. The compiler verifies that every permitted Shape subtype is covered by some case label, making the switch exhaustive. If you add a new permitted Shape implementation without updating the switch expression, the code does not compile.

Guarded patterns extend individual case labels with an additional boolean condition using the when keyword. A guarded pattern matches only when both the type pattern succeeds and the when condition evaluates to true. For a Rectangle, you might have one case that matches squares (when width equals height) and another case that matches all other Rectangles. The when guard is evaluated only after the type match succeeds, making it safe to reference pattern variable properties within the guard expression. Multiple case labels can match the same type with different guards, and the compiler evaluates them in declaration order, executing the body of the first case that fully matches.

The dominance rule is critical knowledge for experienced developer interviews: a more specific case must always appear before a more general case that covers the same type, or the compiler produces an error for the dominated case. If you have a case for Square before a case for Shape, that is valid. But a case for Shape before a case for Square is a compile error because the Shape case dominates the Square case — every Square would match the Shape case first and the Square case would never be reached.

Question 11. What is exhaustiveness in pattern matching and how does the compiler enforce it?

Exhaustiveness means that a switch expression covers every possible value that the switched expression could produce, leaving no case unhandled. For switch expressions over sealed types, the Java compiler performs exhaustiveness checking at compile time by analyzing the sealed hierarchy and verifying that for every possible concrete type in the hierarchy, at least one case label in the switch expression matches that type.

For a sealed interface with three final permitted Record implementations, a switch expression must have case labels covering all three Record types, or include a default case, or include a case null if null is a possible input value. Missing any permitted implementation produces a compile error that explicitly names the uncovered types. This compile-time enforcement is the defining advantage over the pre-Java 21 approach of instanceof chains where adding a new subtype silently fell through to whatever the else branch did, often executing incorrect default behavior for months before the bug was discovered.

The exhaustiveness analysis is recursive for multi-level sealed hierarchies. If a permitted subclass is itself sealed with its own permitted subtypes, the compiler requires coverage of all leaf types throughout the entire hierarchy. A non-sealed permitted subclass breaks exhaustiveness at that node — because any class can extend a non-sealed class, the compiler cannot enumerate all possibilities and requires either a case covering the non-sealed type itself (which covers all its subtypes) or a default case to handle the potentially infinite set of extensions.

Null handling is a subtle but important exhaustiveness consideration that senior developers must know. A switch expression over a reference type does not cover null by default — if the switched expression is null at runtime, a NullPointerException is thrown before any case is evaluated. Java 21 allows an explicit case null label to handle null as a case. For sealed type switches where null is a valid input value, adding case null is the correct approach. Forgetting to handle null when the input may be null is a common source of NullPointerExceptions in pattern matching code that passed all compile-time checks.

Switch Expressions Interview Questions

 

Question 12. What is the difference between a switch statement and a switch expression in Java 21?

Java 21 has two fundamentally different switch constructs with different semantics, different syntax, and different appropriate use cases. Many experienced developers who learned Java before Java 14 conflate these two constructs, and interviewers specifically test whether candidates understand the precise differences.

A switch statement is the traditional Java switch construct that executes code as a side effect without producing a value. It uses colon-style case labels where fall-through between cases is the default behavior unless explicitly prevented with a break statement. A switch statement can modify variables in surrounding scope, call methods, throw exceptions, and perform any executable statement. The result of a switch statement is not a value — it is a sequence of side effects. The lack of exhaustiveness checking in switch statements means the compiler accepts switch statements that do not cover all possible values, silently doing nothing for unhandled cases.

A switch expression is a construct introduced stably in Java 14 and now the preferred form in Java 21. It evaluates to a single value that can be assigned to a variable, returned from a method, passed as an argument, or used in any context requiring an expression. Switch expressions use arrow-style case labels by default (case X -> result) which never fall through between cases — each case is completely independent. The compiler enforces exhaustiveness for switch expressions — every possible input value must be covered by some case or a default case, preventing the silent do-nothing behavior of unhandled switch statement cases.

The critical behavioral difference for interviews is exhaustiveness and fall-through. Switch expressions with arrow labels are exhaustive and never fall through. Switch statements with colon labels are neither exhaustive nor fall-through-free by default. The recommendation for all new Java 21 code is unambiguous: always use switch expressions over switch statements. Switch expressions are safer, more readable, integrate seamlessly with pattern matching, and enable the compiler to catch more bugs at compile time.

Question 13. What is the yield keyword in Java 21 and when is it required?

The yield keyword is used inside a switch expression's block case to provide the value that the case produces. It is specifically a switch expression keyword with no meaning in switch statements or anywhere else in Java code.

Arrow case labels in switch expressions use a concise syntax where the expression directly after the arrow is automatically the value that case produces. For simple single-expression cases, yield is never needed or used. You write case X -> someExpression and someExpression is the case value.

When a case requires multiple statements before determining its result — local variable declarations for intermediate calculations, validation logic, logging, or conditional computation — you use a block case enclosed in curly braces. Inside this block, you write whatever statements are needed and then use yield expressionValue as the final statement to specify the value the switch expression produces for this case. The yield statement ends the block case execution and provides the result, similar in concept to how return provides a method's result but specifically scoped to the containing switch expression.

A common and intentionally tricky interview question is whether yield is a reserved keyword in Java. The answer is that yield is a context-sensitive keyword — it is only recognized as the switch expression yield statement in the specific syntactic context of a switch expression block case. In all other contexts throughout a Java program, yield is a perfectly valid identifier. This design decision means that existing Java code that uses yield as a variable name, method name, or class name continues to compile and run unchanged when that code is not inside a switch expression block, preserving backward compatibility with codebases that predated the yield keyword.

Additional Java 21 Features Interview Questions

 

Question 14. What are Text Blocks in Java 21 and how do they improve code readability?

Text Blocks are a Java feature that became stable in Java 15 and are now used extensively in Java 21 codebases. They allow you to write multi-line string literals without the escape sequences, concatenation, and manual newline characters that made multi-line strings extremely verbose and difficult to read in traditional Java.

A Text Block is delimited by triple-quote characters on both ends. The content between the opening triple-quote (which must be followed by a newline) and the closing triple-quote is treated as a string value with incidental whitespace stripped based on the position of the closing delimiter. Text Blocks preserve the intentional indentation of the content relative to the closing delimiter while stripping the leading whitespace that is simply a consequence of the code's indentation level.

Text Blocks are most impactful for embedding structured text content directly in Java source code: SQL queries that span multiple lines and benefit from indentation that mirrors the query structure, JSON payloads used in tests and configuration, HTML templates, XML configurations, and regular expressions that are long enough to benefit from multi-line formatting. In Spring Boot 3.x applications, Text Blocks are commonly used for native SQL queries in @Query annotations, for JSON test payloads in MockMvc tests, and for Spring AI prompt templates that need multi-paragraph content with preserved formatting.

Question 15. What are Virtual Threads in Java 21 and how are they different from platform threads?

Virtual Threads are the most significant Java 21 feature for backend and server-side developers. They are lightweight threads managed entirely by the JVM rather than by the operating system, introduced as a stable, production-ready feature in Java 21 through Project Loom.

Traditional platform threads map one-to-one with operating system threads. Each platform thread requires approximately one megabyte of stack memory and a corresponding OS thread. Creating and context-switching between platform threads involves expensive OS-level operations. These constraints limit practical concurrency to thousands of threads on a typical server, making thread-per-request architectures inefficient under high concurrency and driving developers toward complex reactive programming models.

Virtual Threads are multiplexed by the JVM onto a small pool of carrier threads — typically one per CPU core. When a Virtual Thread blocks on I/O (a database query, an HTTP request, a file read), the JVM unmounts the virtual thread from its carrier thread, freeing the carrier to run another virtual thread. When the I/O completes, the JVM remounts the virtual thread on an available carrier and resumes its execution from exactly where it paused. This mechanism allows millions of Virtual Threads to exist concurrently with minimal memory overhead, enabling high-concurrency applications with simple blocking code that reads like single-threaded code but scales like reactive code.

The most important interview point about Virtual Threads is thread pinning — the condition where a Virtual Thread cannot be unmounted from its carrier during blocking. Pinning occurs when a Virtual Thread enters a synchronized block or method and then blocks on I/O, and when native code is executing. During pinning, the carrier thread is blocked, negating the scalability benefit. The solution is to replace synchronized blocks with ReentrantLock in code that needs to block on I/O within a critical section. In Spring Boot 3.2 and later, Virtual Threads are enabled with a single property: spring.threads.virtual.enabled=true.

Question 16. What is Structured Concurrency in Java 21 and what problem does it solve?

Structured Concurrency is a Java 21 preview feature that treats multiple concurrent tasks executed as part of a single operation as a unit of work, with a defined lifetime scoped to the unit that spawned them. It addresses the most dangerous and difficult-to-debug class of concurrency bugs: tasks that outlive their logical parent scope, tasks that fail silently when other tasks in the group succeed, and resource leaks when some tasks in a group complete while others are still running.

Before Structured Concurrency, fanning out multiple concurrent tasks and collecting their results required careful management of ExecutorService, Future objects, CompletableFuture chains, and explicit error handling that was easy to get wrong. If one task failed, you had to explicitly cancel the other tasks. If a thread was interrupted, tasks running in other threads might continue indefinitely as orphaned background threads consuming resources.

StructuredTaskScope in Java 21 creates a scope that represents a unit of concurrent work. All tasks forked within the scope are children of that scope. The scope's join() method waits for all child tasks to complete. The scope's close() method (invoked automatically by the try-with-resources construct) cancels any tasks that have not yet completed, guaranteeing that no task outlives the scope. StructuredTaskScope.ShutdownOnFailure cancels all remaining tasks as soon as any task fails, then rethrows the exception after join(). StructuredTaskScope.ShutdownOnSuccess cancels all remaining tasks as soon as any task succeeds, returning the first successful result. These two built-in policies cover the most common concurrent fan-out patterns without any manual task lifecycle management.

Question 17. What is the difference between var and explicit type declarations in Java 21?

The var keyword, introduced in Java 10 and refined through to Java 21, enables local variable type inference — the compiler infers the type of the variable from the initializer expression, eliminating the need to write the type explicitly when it is already obvious from the right-hand side of the assignment.

Var is not dynamic typing — Java remains a statically typed language and var variables have a fixed, compile-time-determined type. The type is determined by the compiler from the initializer and cannot change. var simply instructs the compiler to determine the type rather than requiring the programmer to write it explicitly. The resulting bytecode is identical to code with explicit type declarations.

Var is appropriate when the type is immediately obvious from the initializer (var user = new User()) or when the type is a complex generic type whose explicit declaration would be verbose without adding clarity (var entries = new HashMap<String, List<Order>>()). Var is inappropriate when the initializer is ambiguous about the intended type, when the variable is used as a wide interface type (you want List not ArrayList), when the variable is a class field or method parameter (var is only valid for local variables), and when the inferred type would be a non-denotable type that developers cannot easily determine from reading the code.

In Java 21 codebases, var is commonly used in for-each loops, in local variables that hold the results of method calls where the method name already communicates the type, and in try-with-resources statements. The Java community has converged on a stylistic guideline: use var when it improves readability by reducing verbosity, and use explicit types when they improve readability by making the type visible and unambiguous.

Question 18. What are Sequenced Collections in Java 21 and what new methods do they introduce?

Sequenced Collections are a new set of interfaces introduced in Java 21 that retrofit a consistent encounter order API onto Java's existing collection hierarchy. Before Java 21, Java's collection interfaces had an inconsistent and frustrating API for accessing the first and last elements of ordered collections: List used get(0) and get(list.size()-1), Deque used getFirst() and getLast(), LinkedHashSet had no direct first/last access at all, and SortedSet used first() and last(). These inconsistencies required developers to remember different access patterns for different collection types.

Java 21 introduces three new interfaces. SequencedCollection extends Collection and adds getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), and reversed(). SequencedSet extends SequencedCollection and Set, providing the same methods for sets that maintain insertion or sorted order. SequencedMap extends Map and adds firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(), putLast(), and reversed(). These interfaces are retroactively added to existing collection classes — List, Deque, LinkedHashSet, LinkedHashMap, SortedSet, SortedMap, and their implementations — making the new methods immediately available on all existing Java code that uses these types.

The reversed() method returns a reversed view of the collection without copying it — a live view where changes to the original are visible in the reversed view and vice versa. This is particularly useful for iterating a collection in reverse order without creating a copy and without the awkward workarounds previously required for different collection types.

Question 19. What is the difference between a preview feature and a stable feature in Java 21 and can preview features be used in production?

Java's preview feature mechanism allows language features and APIs to be included in a JDK release in a state where they are complete enough for broad developer feedback but not yet considered final and permanent. Preview features may change or be removed in future Java releases based on feedback received. A feature that receives positive feedback and minimal change requests typically becomes stable in the next one or two Java versions after its preview introduction.

Preview features require explicit opt-in at both compile time and runtime using the --enable-preview flag. At compile time: javac --enable-preview --release 21 MyClass.java. At runtime: java --enable-preview MyClass. This explicit opt-in prevents preview features from being accidentally used in production code and ensures developers consciously accept the risk that the feature may change.

In Java 21, the most notable preview features are Structured Concurrency (StructuredTaskScope and related APIs) and String Templates (a feature for embedding expressions in string literals, similar to string interpolation in other languages). These are preview in Java 21 and subject to change.

Stable features in Java 21 include Records, Sealed Classes, Pattern Matching for instanceof, Pattern Matching for switch, Virtual Threads, Text Blocks, Sequenced Collections, and the enhanced switch expression. These features are permanently part of the Java language specification and can be used in production code without any flags.

The practical guidance: do not use preview features in production systems unless your team explicitly accepts the risk that the feature API may change in the next Java release, requiring code changes during the next JDK upgrade. For production code, use only stable features.

Question 20. How does Java 21 improve JVM performance compared to earlier LTS versions?

Java 21 delivers performance improvements through several interconnected enhancements that collectively make Java 21 LTS applications meaningfully faster and more resource-efficient than equivalent applications running on Java 11 or Java 17 LTS.

The Generational ZGC enhancement in Java 21 makes ZGC — the ultra-low-latency garbage collector that provides sub-millisecond GC pause times — generationally aware. Generational ZGC maintains separate memory regions for young objects (which are collected frequently) and old objects (which are collected less frequently), similar to the generational approach used by G1GC. This generational awareness significantly reduces GC overhead for applications where most objects are short-lived — which describes the majority of web application workloads where request-scoped objects are created and discarded per request.

Virtual Threads improve performance for I/O-bound applications not by making individual operations faster but by enabling much higher concurrency with the same resources. A Spring Boot application handling 10,000 concurrent requests with Virtual Threads uses a small number of carrier threads (typically equal to the CPU core count) rather than 10,000 platform threads. This dramatically reduces OS-level context switching overhead, reduces memory consumption from thread stacks, and eliminates the thread pool sizing problem that traditionally required careful tuning.

The continued improvements to the JIT compiler (C2) in Java 21 improve throughput for computation-heavy workloads through better inlining decisions, improved escape analysis that eliminates unnecessary heap allocations for short-lived objects, and enhanced vectorization of numerical operations. Pattern matching and switch expressions in Java 21 are compiled to more efficient bytecode than equivalent if-instanceof-cast chains, providing minor but consistent throughput improvements for code that uses these features extensively. The combination of Generational ZGC, Virtual Threads, and JIT improvements makes Java 21 the most performant Java LTS release available and provides strong justification for migrating from Java 11 or Java 17.

Ready to Master Java 21 and Land Your Dream Java Role?

 

Understanding Java 21 features at the depth required for senior developer interviews takes more than reading documentation. It requires building real projects that use Records, Sealed Classes, Pattern Matching, and Virtual Threads in production-realistic contexts — designing domain models, implementing business logic, debugging edge cases, and explaining every decision under interview pressure.

JustAcademy's Full Stack Java Developer Bootcamp provides exactly that — expert-led, live, interactive training covering Java 21, Spring Boot 3.x, microservices, and everything needed to pass senior Java interviews and land high-paying full-stack Java roles in 2026. With real-world projects, mock interview preparation, and 100% placement support through 650+ hiring partners, it is the most complete Java developer career accelerator available today.

Enroll here and start your Java 21 mastery journey.

Frequently Asked Questions

 

Are Java 21 features tested in fresher interviews or only for experienced developers?

Freshers interviewing for junior Java roles in 2026 should have awareness of Records, Text Blocks, and the basics of switch expressions at minimum since Java 21 is now the baseline JDK at most companies. Mid-level developers with two to four years of experience are expected to understand Records, Sealed Classes, Pattern Matching for instanceof, and Virtual Threads deeply, with the ability to explain why each feature exists and what problem it solves. Senior developers with five or more years are expected to explain all Java 21 features including the nuances of exhaustiveness checking, guarded patterns, thread pinning, and the design philosophy behind each feature. Every question in this guide is relevant at some experience level, and understanding all 20 answers gives freshers a significant competitive advantage over candidates who only know the basics.

What is the most commonly asked Java 21 interview question for experienced developers?

Based on current interview feedback from Java developers at product companies in 2026, the most commonly asked Java 21 combination question is: explain how Sealed Classes and Pattern Matching for switch work together, write a code example, and explain what the compiler enforces at compile time. This question simultaneously tests Records (as sealed interface implementations), Sealed Classes (the hierarchy root), Pattern Matching for switch (the consumer of the hierarchy), exhaustiveness checking (the compiler's enforcement mechanism), and guarded patterns (advanced switch expression usage). It is the single question that most completely reveals whether a developer has practical production experience with Java 21's type system or has only studied the features individually.

What is the best way to practice Java 21 features before a senior interview?

The most effective practice path combines conceptual study with deliberate code writing. Read the official Java Enhancement Proposals for each feature: JEP 395 for Records, JEP 409 for Sealed Classes, JEP 441 for Pattern Matching for switch, and JEP 444 for Virtual Threads. Then build a complete domain model that uses all features together: a sealed interface hierarchy of domain events implemented as Records, a switch expression for event processing with guarded patterns, and a service that handles requests using Virtual Threads. Deliberately trigger compiler errors around exhaustiveness and dominance to internalize the rules through experience rather than memorization. Explain every design decision out loud to practice articulating the reasoning that senior interviewers evaluate.

Do I need to memorize code syntax for a Java 21 interview?

You do not need to write perfect syntactically correct code under interview pressure, but you need to be fluent enough to sketch the key concepts accurately. Interviewers evaluate whether you understand what the code does and why, not whether you remember every character of the syntax. For Java 21 specifically, being able to sketch a Record declaration, a sealed interface with permits clause, a switch expression with pattern matching cases, and a Virtual Thread creation demonstrates sufficient fluency. The conceptual explanation of what the compiler enforces and why the feature was designed that way is weighted more heavily than perfect syntax in most senior developer interviews.

Conclusion

Java 21's Records, Sealed Classes, Pattern Matching, Switch Expressions, Virtual Threads, Text Blocks, and Sequenced Collections represent the most significant evolution of the Java language in a decade. The developers who understand these features at interview depth in 2026 — who can explain not just what each feature does but why it was designed that way, what its limitations are, and how it combines with other features to enable better code — are the candidates who pass senior Java interviews at top product companies.

The 20 questions and detailed answers in this guide provide that depth across every major Java 21 feature area. Study them with active engagement — write the code examples, trigger the compiler errors, build the domain models, and practice explaining the reasoning out loud. Java 21 interviews reward understanding over memorization, and every answer that connects a feature to a real-world design decision demonstrates the kind of depth that gets senior Java developers hired.

Ready to make Java 21 mastery your career advantage? JustAcademy's Full Stack Java Developer Bootcamp covers Java 21, Spring Boot 3.x, microservices, and everything you need to pass senior Java interviews and land high-paying roles in 2026 with real projects, expert mentorship, and 100% placement support.

Related Bootcamps

Full Stack Mobile App Development Bootcamp (Flutter, Node.js, MongoDB, Express)

MERN Stack Developer Bootcamp

MEAN Stack Developer Bootcamp

JustAcademy | 1201, 12th Floor, Star Plaza, Borivali East, Mumbai 400066 | +91 99871 84296 | www.justacademy.co

Connect With Us
whatsapp